using System;
using System.Text;
using System.Globalization;
using System.Threading;

namespace nToolbox
{
	public class DateTimeHelper
	{
		/// <summary>
		/// Checks if a date is in the past.
		/// </summary>
		/// <remarks>Units smaller than day (hour, minute, second, millisecond) are ignored.</remarks>
		public static bool IsDateInPast(DateTime dt)
		{
			DateTime currentDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
			DateTime compareDate = new DateTime(dt.Year, dt.Month, dt.Day);

			return DateTime.Compare(currentDate, compareDate) == 1;
		}

		/// <summary>
		/// Checks if a date is in the past.
		/// </summary>
		/// <remarks>Units smaller than day (hour, minute, second, millisecond) are ignored.</remarks>
		public static bool IsDateInFuture(DateTime dt)
		{
			DateTime currentDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
			DateTime compareDate = new DateTime(dt.Year, dt.Month, dt.Day);

			return DateTime.Compare(currentDate, compareDate) == -1;
		}

		/// <summary>
		/// Returns the earliest date of the pair.
		/// </summary>
		public static DateTime EarliestDate(DateTime dt1, DateTime dt2)
		{
			return DateTime.Compare(dt1, dt2) == 1 ? dt2 : dt1;
		}

		/// <summary>
		/// Returns the most recent date of the pair.
		/// </summary>
		public static DateTime LatestDate(DateTime dt1, DateTime dt2)
		{
			return DateTime.Compare(dt1, dt2) == 1 ? dt1 : dt2;
		}

		/// <summary>
		/// Determines if two days fall in the same month
		/// </summary>
		public static bool DatesAreInSameMonth(DateTime dt1, DateTime dt2)
		{
			return (dt1.Month.Equals(dt2.Month));
		}

		/// <summary>
		/// Determines if two days fall in the same year
		/// </summary>
		public static bool DatesAreInSameYear(DateTime dt1, DateTime dt2)
		{
			return (dt1.Year.Equals(dt2.Year));
		}

		/// <summary>
		/// Determines if two dates fall in the same month in the same year.
		/// </summary>
		public static bool DatesAreInSameMonthAndYear(DateTime dt1, DateTime dt2)
		{
			return DatesAreInSameMonth(dt1, dt2) && DatesAreInSameYear(dt1, dt2);
		}

		/// <summary>
		/// Gets the number of days left in the month
		/// </summary>
		public static int DaysRemainingInMonth(DateTime dt)
		{
			Calendar cal = Thread.CurrentThread.CurrentCulture.Calendar;
			int era = cal.GetEra(dt);
			return cal.GetDaysInMonth(dt.Year, dt.Month, era) - dt.Day;
		}

		/// <summary>
		/// Gets the number of days left in the current month
		/// </summary>
		public static int DaysRemainingInMonth()
		{
			return DaysRemainingInMonth(DateTime.Now);
		}

		/// <summary>
		/// Gets the number of days left in the year
		/// </summary>
		public static int DaysRemainingInYear(DateTime dt)
		{
			Calendar cal = Thread.CurrentThread.CurrentCulture.Calendar;
			return cal.GetDaysInYear(dt.Year, cal.GetEra(dt)) - dt.DayOfYear;
		}

		/// <summary>
		/// Gets the number of days left in the current year
		/// </summary>
		public static int DaysRemainingInYear()
		{
			return DaysRemainingInYear(DateTime.Now);
		}

		/// <summary>
		/// Gets the first occurence of a given day in a month/year
		/// </summary>
		/// <param name="day">DayOfWeek to find</param>
		/// <param name="month">Month to scan</param>
		/// <param name="year">Year to scan</param>
		/// <returns>int representing the day</returns>
		public static int FirstOccurenceOfDayInMonth(DayOfWeek day, int month, int year)
		{
			DateTime dt = new DateTime(year, month, 1);
			int d = 0;

			for (int i = 0; i < 7; i++)
			{
				if (dt.AddDays(i).DayOfWeek == day)
				{
					d = i + 1;
					break;
				}
			}

			return d;
		}

		/// <summary>
		/// Gets the last occurence of a given day in a month/year
		/// </summary>
		/// <param name="day">DayOfWeek to find</param>
		/// <param name="month">Month to scan</param>
		/// <param name="year">Year to scan</param>
		/// <returns>int representing the day</returns>
		public static int LastOccurenceOfDayInMonth(DayOfWeek day, int month, int year)
		{
			int d = DateTime.DaysInMonth(year, month);
			DateTime dt = new DateTime(year, month, d);


			for (int i = 0; i < 7; i++)
			{
				if (dt.AddDays(-i).DayOfWeek == day)
				{
					d = d - i;
					break;
				}
			}

			return d;
		}

		/// <summary>
		/// Converts the day of the year to a date
		/// </summary>
		/// <remarks>
		/// For example day 32 would February 1.
		/// </remarks>
		public static DateTime ConvertDayOfYearToDate(int dayOfYear, int year)
		{
			DateTime dt = new DateTime(year, 1, 1);
			return dt.AddDays(dayOfYear - 1);
		}

        /// <summary>
        /// Take a date range and return an hour:minute:second string, eg: "04:34:22" for 4 hours, 34 minutes, 22 seconds
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns>
        /// </returns>        
        public static string DiffToHourMinuteSeconds(DateTime startTime, DateTime endTime)
        {
            double seconds = Diff(endTime, startTime).TotalSeconds;
            int hours = Convert.ToInt32(seconds / 3600);
            seconds = seconds % 3600;
            int minutes = Convert.ToInt32(seconds / 60);
            seconds = seconds % 60;

            StringBuilder strReturn = new StringBuilder();
            strReturn.Append(string.Format("{0:00}", hours));
            strReturn.Append(":");
            strReturn.Append(string.Format("{0:00}", minutes));
            strReturn.Append(":");
            strReturn.Append(string.Format("{0:00}", seconds));
            return strReturn.ToString();
        }
        
        #region SubSonic Project Methods

        #region Date Math
                
        /// <summary>
        /// Get the date by subtracting the number of days from the current date
        /// </summary>
        /// <param name="days">The days.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime DaysAgo(int days)
        {
            TimeSpan t = new TimeSpan(days, 0, 0, 0);
            return DateTime.Now.Subtract(t);
        }

        /// <summary>
        /// Dayses from now.
        /// </summary>
        /// <param name="days">The days.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime DaysFromNow(int days)
        {
            TimeSpan t = new TimeSpan(days, 0, 0, 0);
            return DateTime.Now.Add(t);
        }

        /// <summary>
        /// Hourses the ago.
        /// </summary>
        /// <param name="hours">The hours.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime HoursAgo(int hours)
        {
            TimeSpan t = new TimeSpan(hours, 0, 0);
            return DateTime.Now.Subtract(t);
        }

        /// <summary>
        /// Hourses from now.
        /// </summary>
        /// <param name="hours">The hours.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime HoursFromNow(int hours)
        {
            TimeSpan t = new TimeSpan(hours, 0, 0);
            return DateTime.Now.Add(t);
        }

        /// <summary>
        /// Minuteses the ago.
        /// </summary>
        /// <param name="minutes">The minutes.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime MinutesAgo(int minutes)
        {
            TimeSpan t = new TimeSpan(0, minutes, 0);
            return DateTime.Now.Subtract(t);
        }

        /// <summary>
        /// Minuteses from now.
        /// </summary>
        /// <param name="minutes">The minutes.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime MinutesFromNow(int minutes)
        {
            TimeSpan t = new TimeSpan(0, minutes, 0);
            return DateTime.Now.Add(t);
        }

        /// <summary>
        /// Secondses the ago.
        /// </summary>
        /// <param name="seconds">The seconds.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime SecondsAgo(int seconds)
        {
            TimeSpan t = new TimeSpan(0, 0, seconds);
            return DateTime.Now.Subtract(t);
        }

        /// <summary>
        /// Seconds from now.
        /// </summary>
        /// <param name="seconds">The seconds.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static DateTime SecondsFromNow(int seconds)
        {
            TimeSpan t = new TimeSpan(0, 0, seconds);
            return DateTime.Now.Add(t);
        }
        #endregion

        #region Diffs

        /// <summary>
        /// Diffs the specified date one.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static TimeSpan Diff(DateTime dateOne, DateTime dateTwo)
        {
            TimeSpan t = dateOne.Subtract(dateTwo);
            return t;
        }

        /// <summary>
        /// Diffs the days.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffDays(string dateOne, string dateTwo)
        {
            DateTime dtOne;
            DateTime dtTwo;
            if (DateTime.TryParse(dateOne, out dtOne) && DateTime.TryParse(dateTwo, out dtTwo))
            {
                return Diff(dtOne, dtTwo).TotalDays;
            }
            return 0;
        }

        /// <summary>
        /// Diffs the days.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffDays(DateTime dateOne, DateTime dateTwo)
        {
            return Diff(dateOne, dateTwo).TotalDays;
        }

        /// <summary>
        /// Diffs the hours.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffHours(string dateOne, string dateTwo)
        {
            DateTime dtOne;
            DateTime dtTwo;
            if (DateTime.TryParse(dateOne, out dtOne) && DateTime.TryParse(dateTwo, out dtTwo))
            {
                return Diff(dtOne, dtTwo).TotalHours;
            }
            return 0;
        }

        /// <summary>
        /// Diffs the hours.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffHours(DateTime dateOne, DateTime dateTwo)
        {
            return Diff(dateOne, dateTwo).TotalHours;
        }

        /// <summary>
        /// Diffs the minutes.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffMinutes(string dateOne, string dateTwo)
        {
            DateTime dtOne;
            DateTime dtTwo;
            if (DateTime.TryParse(dateOne, out dtOne) && DateTime.TryParse(dateTwo, out dtTwo))
            {
                return Diff(dtOne, dtTwo).TotalMinutes;
            }
            return 0;
        }

        /// <summary>
        /// Diffs the minutes.
        /// </summary>
        /// <param name="dateOne">The date one.</param>
        /// <param name="dateTwo">The date two.</param>
        /// <returns></returns>
        /// <remarks>
        /// Thanks to theSubsonic Project
        /// http://www.subsonicproject.com/
        /// Used under the Mozilla Public License 1.1
        /// http://www.codeplex.com/subsonic/Project/License.aspx
        /// </remarks>
        public static double DiffMinutes(DateTime dateOne, DateTime dateTwo)
        {
            return Diff(dateOne, dateTwo).TotalMinutes;
        }
        
        /// <summary>
        /// Displays the difference in time between the two dates. Return example is "12 years 4 months 24 days 8 hours 33 minutes 5 seconds"
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>        
        public static string ReadableDiff(DateTime startTime, DateTime endTime)
        {
            StringBuilder result = new StringBuilder();

            int seconds = endTime.Second - startTime.Second;
            int minutes = endTime.Minute - startTime.Minute;
            int hours = endTime.Hour - startTime.Hour;
            int days = endTime.Day - startTime.Day;
            int months = endTime.Month - startTime.Month; 
            int years = endTime.Year - startTime.Year;

            if (seconds < 0) 
            {
                minutes--;
                seconds += 60;
            }
            if (minutes < 0)
            {
                hours--;
                minutes += 60;
            }
            if (hours < 0)
            {
                days--;
                hours += 24;
            }

            if (days < 0)
            {
                months--;
                int previousMonth = (endTime.Month == 1) ? 12 : endTime.Month - 1;
                int year = (previousMonth == 12) ? endTime.Year - 1 : endTime.Year;
                days += DateTime.DaysInMonth(year, previousMonth);
            }
            if (months < 0)
            {
                years--;
                months += 12;
            }

            //put this in a readable format
            if (years > 0)
            {
                result.Append(years.ToString());
                result.Append(" year");
                if (years != 1)
                    result.Append("s");
            }
            if (months > 0)
            {
                if (years > 0)
                    result.Append(", ");
                result.Append(months.ToString());
                result.Append(" month");
                if (months != 1)
                    result.Append("s");
            }
            if (days > 0)
            {
                if (years > 0 || months > 0)
                    result.Append(", ");
                result.Append(days.ToString());
                result.Append(" day");
                if (days != 1)
                    result.Append("s");
            }
            if (hours > 0)
            {
                if (years > 0 || months > 0 || days > 0)
                    result.Append(", ");
                result.Append(hours.ToString());
                result.Append(" hour");
                if (hours != 1)
                    result.Append("s");
            }
            if (minutes > 0)
            {
                if (years > 0 || months > 0 || days > 0 || hours > 0)
                    result.Append(", ");
                result.Append(minutes.ToString());
                result.Append(" minute");
                if (minutes != 1)
                    result.Append("s");
            }
            if (seconds > 0)
            {
                if (years > 0 || months > 0 || days > 0 || hours > 0 || minutes > 0)
                    result.Append(", ");
                result.Append(seconds.ToString());
                result.Append(" second");
                if (seconds != 1)
                    result.Append("s");
            }
            result.Append(" ago");
            return result.ToString();

        }

        #endregion

        #region ASP Alliance Methods
        
        //many thanks to ASP Alliance for the code below
        //http://authors.aspalliance.com/olson/methods/
        /// <summary>
        /// Counts the number of weekdays between two dates.
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns></returns>
        public static int CountWeekdays(DateTime startTime, DateTime endTime)
        {
            TimeSpan ts = endTime - startTime;
            Console.WriteLine(ts.Days);
            int cnt = 0;
            for (int i = 0; i < ts.Days; i++)
            {
                DateTime dt = startTime.AddDays(i);
                if (IsWeekDay(dt))
                {
                    cnt++;
                }
            }

            return cnt;
        }

        /// <summary>
        /// Counts the number of weekends between two dates.
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns></returns>
        public static int CountWeekends(DateTime startTime, DateTime endTime)
        {
            TimeSpan ts = endTime - startTime;
            Console.WriteLine(ts.Days);
            int cnt = 0;
            for (int i = 0; i < ts.Days; i++)
            {
                DateTime dt = startTime.AddDays(i);
                if (IsWeekEnd(dt))
                {
                    cnt++;
                }
            }

            return cnt;
        }
      
        /// <summary>
        /// Verifies if the object is a date
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static bool IsDate(object dt)
        {
            DateTime newDate;
            return DateTime.TryParse(dt.ToString(), out newDate);
        }

        /// <summary>
        /// Checks to see if the date is a week day (Mon - Fri)
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static bool IsWeekDay(DateTime dt)
        {
            return Convert.ToInt16(dt.DayOfWeek) < 6;
        }

        /// <summary>
        ///  Checks to see if the date is Sat or Sunday
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static bool IsWeekEnd(DateTime dt)
        {
            return Convert.ToInt16(dt.DayOfWeek) > 5;
        }

        /// <summary>
        /// Displays the difference in time between the two dates. Return example is "12 years 4 months 24 days 8 hours 33 minutes 5 seconds"
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns></returns>
        public static string TimeDiff(DateTime startTime, DateTime endTime)
        {
            int seconds = endTime.Second - startTime.Second;
            int minutes = endTime.Minute - startTime.Minute;
            int hours = endTime.Hour - startTime.Hour;
            int days = endTime.Day - startTime.Day;
            int months = endTime.Month - startTime.Month;
            int years = endTime.Year - startTime.Year;
            if (seconds < 0)
            {
                minutes--;
                seconds += 60;
            }
            if (minutes < 0)
            {
                hours--;
                minutes += 60;
            }
            if (hours < 0)
            {
                days--;
                hours += 24;
            }
            if (days < 0)
            {
                months--;
                int previousMonth = (endTime.Month == 1) ? 12 : endTime.Month - 1;
                int year = (previousMonth == 12) ? endTime.Year - 1 : endTime.Year;
                days += DateTime.DaysInMonth(year, previousMonth);
            }
            if (months < 0)
            {
                years--;
                months += 12;
            }

            string sYears = FormatString("year", string.Empty, years);
            string sMonths = FormatString("month", sYears, months);
            string sDays = FormatString("day", sMonths, days);
            string sHours = FormatString("hour", sDays, hours);
            string sMinutes = FormatString("minute", sHours, minutes);
            string sSeconds = FormatString("second", sMinutes, seconds);

            return sYears + sMonths + sDays + sHours + sMinutes + sSeconds;
        }

        /// <summary>
        /// Given a datetime object, returns the formatted month and day, i.e. "April 15th" 
        /// </summary>
        /// <param name="date">The date to extract the string from</param>
        /// <returns></returns>
        public static string GetFormattedMonthAndDay(DateTime date)
        {
            return String.Format("{0:MMMM}", date) + " " + GetDateDayWithSuffix(date);
        }

        /// <summary>
        /// Given a datetime object, returns the formatted day, "15th" 
        /// </summary>
        /// <param name="date">The date to extract the string from</param>
        /// <returns></returns>
        public static string GetDateDayWithSuffix(DateTime date)
        {
            int dayNumber = date.Day;
            if (dayNumber == 1 || dayNumber == 21 || dayNumber == 31)
            {
                return dayNumber + "st";
            }
            else if (dayNumber == 2 || dayNumber == 22)
            {
                return dayNumber + "nd";
            }
            else if (dayNumber == 3 || dayNumber == 23)
            {
                return dayNumber + "rd";
            }
            return dayNumber + "th";
        }

        //remove leading strings with zeros and adjust for singular/plural
        protected internal static string FormatString(string str, string previousStr, int t)
        {
            if ((t == 0) && (previousStr.Length == 0)) return string.Empty;
            if (t == 1)
            {
                str = t + " " + str;
            }
            else
            {
                str = t + " " + str + "s";
            }
            return str + " ";
        }
        #endregion


        #endregion
        
	}
}